类的加载
加载
连接
2.1 校验:例如检查JDK 版本,检查字节码是否以 “魔数 cafe” 开头等。
2.2 准备:给成员变量(类变量,静态变量)赋于默认值。把常量(final)等值在方法区的常量池中给准备好。
2.3 解析:理解为把类中的类型名转换成该类型的 Class 对象的地址。例如,将 String 转换为 String 类型对应的 Class 地址。
初始化: <clinit>
类初始化,<clinit>
类初始化会执行两部分内容,分别是静态变量的显式赋值和静态代码块的内容。当一个类初始化时,如果发现它的父类没有被初始化,那么会先初始话父类。每一个类只会初始化一次, 所以是线程安全的(所以在单例中的饿汉式是线程安全的)。
注意⚠️:类的加载不一定会发生类的初始化。只是大部分类在加载的时候会发生类的初始化而已。 那么哪些操作时引发类的初始化 呢?
main 方法所在的类在加载时,直接就先初始话。
1 2 3 4 5 6 7 8 9 10 11 package com.itguigu.singleton;public class TestLoader { static { System.out.println("main 方法所在的类会先初始化" ); } public static void main (String[] args) { } }
new 一个类的对象,一定会先完成类的初始化。
调用该类的静态变量(final 的常量除外),或者静态方法。
使用 java.lang.reflect 包的方法对类进行反射调用。
当初始化一个类的时候,如果发现它的父类没有被初始化,那么会初始化它的父类。
哪些操作不会引起类的初始化 ?
当引用静态的常量(final )时,不会引起类的初始化。原因是常量在类加载的连接中的准备阶段就已经在方法区的常量池中给准备好了。
当访问一个静态域时,只有真正声明这个域的类才会被初始化。换句话说,通过子类访问父类的静态域时(静态的东西),只会初始化父类,不会初始化子类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.itguigu.singleton;public class TestLoader { public static void main (String[] args) { System.out.println(Son.number); } } class Father { public static int number = 1 ; static { System.out.println("父类静态代码块" ); } } class Son extends Father { static { System.out.println("子类静态代码块" ); } }
通过数组定义类引用,不会触发此类的初始化。
1 2 3 4 5 6 7 8 9 10 11 12 package com.itguigu.singleton;public class TestLoader { public static void main (String[] args) { MyClass[] myClasses = new MyClass[6 ]; } } class MyClass {}
获取 Class 对象
类型名.class。注意:基本数据类型和 void 只能通过这种方式获取。
对象.getClass()。获取到的是对象的运行时类型,只能用于引用数据类型,不能是基本数据类型,基本数据类型是不能 new 对象的。
Class.forName(“类的全名称”)。只能用于引用数据类型。
类加载器对象.loadClass(“类的全名称”)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package com.itguigu.singleton;import org.junit.Test;public class testClass { @Test public void test1 () { Class class1 = int .class ; Class class2 = Void.class ; Class class3 = String.class ; Class class4 = Object.class ; Class class5 = Comparable.class ; System.out.println(class1); System.out.println(class2); System.out.println(class3); System.out.println(class4); System.out.println(class5); } @Test public void test2 () { String string = "zhangsan" ; Class class1 = string.getClass(); Integer integer = 1 ; Class class2 = integer.getClass(); System.out.println(class1); System.out.println(class2); } @Test public void test3 () throws ClassNotFoundException { Class class1 = Class.forName("java.lang.String" ); Class class2 = Class.forName("java.lang.Comparable" ); System.out.println(class1); System.out.println(class2); } @Test public void test4 () throws ClassNotFoundException { ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); Class class1 = systemClassLoader.loadClass("java.lang.String" ); System.out.println(class1); } }
类加载器 类加载器是负责加载类的对象
每个 Class 对象都包含一个对定义它的 ClassLoader 的引用,即通过 Class 对象可以得到加载它的类加载对象。
类加载器的分类
引导类加载器(Bootstrap Classloader)又称为根类加载器,负责加载 Jaca 核心库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.itguigu.singleton;import org.junit.Test;public class TestClassLoader { @Test public void test1 () { Class class1 = String.class ; ClassLoader classLoader = class1.getClassLoader(); System.out.println(classLoader); } }
扩展类加载器(Extension ClassLoader)主要负责加载 JAVA_HOME/jre/ext/*.jar。下的 jar 包文件。
应用程序类加载器(Application ClassLoader)负责加载 classpath 下的(也就是自己实现的)类。
自定义类加载器。例如 tomcat。一班字节码需要加解密的时候需要用到自定义类加载器,还有就是加载特定目录下的类的时候会用到。
4 认为 3 是它的父加载器,3 会认为2 是它的父加载器,一直网上。 Java 的类加载的过程是一个双亲(parent)委托模式加载的。举例如下:当 “应用程序类加载器” 接收到一个加载任务时,首先会去内存搜索是否已经加载过,如果没有那么就将任务提交给它的父加载器,父加载器收到任务时,也是重复上面的步骤,直到找到根加载器。如果根加载器还没有找到,那么就将任务回复回传,直到又回到了 “应用程序类加载器” ,如果此时都还没有找到,那么就抛出异常。
类加载器加载资源文件 类加载器的作用
最主要的作用是加载类
辅助作用是加载类路径下的资源文件,例如加载 src 下的文件
例子1:使用 ClassLoader 加载 src 下面的文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.itguigu.singleton;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.util.Properties;import org.junit.Test;public class TestLoadFile { @Test public void test1 () throws IOException { Properties properties = new Properties(); properties.load(ClassLoader.getSystemResourceAsStream("test.properties" )); System.out.println(properties); } @Test public void test2 () throws FileNotFoundException, IOException { Properties properties = new Properties(); properties.load(new FileInputStream("test_out.properties" )); System.out.println(properties); } }
在 Java SE 中使用 ClassLoader.getSystemResourceAsStream 加载 src 下面的文件是没有问题的,因为用到的是应用程序类加载器去加载的文件。而这种方法在 Java EE 阶段就会有问题,因为 EE 阶段的类路径在 WEB-INF/classes 下,必须由自定义类加载器去加载。下面这种方法 SE 和 EE 都适用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.itguigu.singleton;import java.io.IOException;import java.io.InputStream;import java.util.Properties;import org.junit.Test;public class TestLoadFile { @Test public void test1 () throws IOException { Properties properties = new Properties(); ClassLoader classLoader = TestLoadFile.class .getClassLoader () ; InputStream resourceAsStream = classLoader.getResourceAsStream("test.properties" ); properties.load(resourceAsStream); System.out.println(properties); } }
例子2:需要加载的文件在 src 下面的包中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.itguigu.singleton;import java.io.IOException;import java.io.InputStream;import java.util.Properties;import org.junit.Test;public class TestLoadFile { @Test public void test1 () throws IOException { Properties properties = new Properties(); ClassLoader classLoader = TestLoadFile.class .getClassLoader () ; InputStream resourceAsStream = classLoader.getResourceAsStream("com/itguigu/singleton/test_in.properties" ); properties.load(resourceAsStream); System.out.println(properties); } }
例子3:新建一个 Source Floder ,加载里面的文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.itguigu.singleton;import java.io.IOException;import java.io.InputStream;import java.util.Properties;import org.junit.Test;public class TestLoadFile { @Test public void test1 () throws IOException { Properties properties = new Properties(); ClassLoader classLoader = TestLoadFile.class .getClassLoader () ; InputStream resourceAsStream = classLoader.getResourceAsStream("config.properties" ); properties.load(resourceAsStream); System.out.println(properties); } }
反射 在运行时可以获取类的信息,对类进行操作,而且该类可能是在编译时完全未知的类型。使 Java 具有动态语言的特性。
反射的作用 1. 在运行期间动态的获取某个类的详细信息 Class 类的 API
Package getPackage() ,获取包信息
int getModifiers() ,获取类的修饰符
getName() ,获取类名称
getSupperclass() ,获取父类
getInterfaces() ,获取父接口,返回的是 Class数组。
Field[] getFields() ,获得某个类的所有的公共(public)的字段,包括父类中的字段。
Field getField(String name) ,根据名称,获得某个类的某个的公共(public)的字段,可以是父类中的字段
Field[] getDeclaredFields() ,获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
Field getDeclaredField() ,获取指定的字段,包括public、private和proteced,但是不包括父类的申明字段。
Constructor<?>[] getConstructors() ,获得某个类的所有的公共(public)的构造器,包括父类中的。
Constructor getConstructor(Class<?>… parameterTypes) ,根据参数类型获得某个类的某个的公共(public)的构造器,可以父类中的。
Constructor<?> [] getDeclaredConstructors() ,获取某个类的所有构造器,包括public、private和proteced,但是不包括父类的申明的。
Constructor getDeclaredConstructor(Class<?>… parameterTypes) ,根据参数类型获取某个类的某个构造器,包括public、private和proteced,但是不包括父类的申明的。
Method[] getMethods()
Method getMethod(String name, Class<?>… parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethods(String name, Class<?>… parameterTypes)
java.lang.refect
Package 类型
getName(),获取包名称
Modifier 类型
调用 toString 能获取到字符串格式的修饰符
Field 类型
Field 代表属性,每一个 Field 的对象代表一个类中的某一个属性。可以调用 getModifiers 获取属性的修饰符,getType 获取属性的类型,getName 获取属性的名称。
Constructor 类型
Constructor 代表构造器,可以调用 getModifiers 获取修饰符,getName 获取名称。getParameterTypes 获取行参列表
Method 类型
Method 代表方法,可以调用 getModifiers 获取修饰符,getName 获取名称。getReturnType 获取返回值类型。getParameterTypes 获取行参列表
2. 在运行期间动态的创建任意类型的对象 newInstance 使用 newInstance 的前提是这个类型必须要有无参构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.itguigu.api;import org.junit.Test;public class TestNewInstance { @Test public void Test1 () throws ClassNotFoundException, InstantiationException, IllegalAccessException { Class<?> classzz = Class.forName("java.lang.String" ); Object object = classzz.newInstance(); System.out.println(object); } }
获取构载器,在创建对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.itguigu.api;import java.lang.reflect.Constructor;import org.junit.Test;public class TestNewInstance { @Test public void Test2 () throws Exception { Class<?> classzz = Class.forName("java.lang.String" ); Constructor<?> constructor = classzz.getDeclaredConstructor(StringBuffer.class ) ; Object object = constructor.newInstance(new StringBuffer("zhangsan" )); System.out.println(object); } }
上面两种方法各有优缺点,但是我们在创建类的时候,尽量保证类的无参构造。
3. 在运行期间动态的获取或修改属性的值 这也是数据库框架 MyBatis,Hibernate 将数据变成 Java Bean 的原理。
动态的获取或修改属性的值步骤如下:
获取 Class 对象
获取 Field 对象
根据 Class 对象创建实例对象
调用 Field对象.set(实例对象,属性值) 进行属性值的修改
调用 Field对象.get(实例对象) 进行属性值的获取
要值得注意的是,如果属性是私有的,那么调用 set 方法会出现访问错误,这时需要调用 Field对象.setAccessible 方法将对象设置为可访问。
在演示 “运行期间动态的获取或修改属性的值” 之前,我们创建了一个类,并将其打包成为了 jar 包存放在 /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext
目录下,类中只有一个私有的 name 属性,还有其他 get set 方法,有参和无参构造等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.itguigu.api;import java.lang.reflect.Field;import org.junit.Test;public class TestNewInstance { @Test public void Test2 () throws Exception { Class<?> classzz = Class.forName("com.itguigu.ext.demo.Student" ); Field nameField = classzz.getDeclaredField("name" ); Object student = classzz.newInstance(); nameField.setAccessible(true ); nameField.set(student, "张三" ); Object fieldValue = nameField.get(student); System.out.println(fieldValue); } }
4. 在运行期间动态的调用任意对象的任意方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.itguigu.api;import java.lang.reflect.Method;import org.junit.Test;public class TestNewInstance { @Test public void Test2 () throws Exception { Class<?> classzz = Class.forName("com.itguigu.ext.demo.Student" ); Method method = classzz.getDeclaredMethod("setName" , String.class ) ; Object student = classzz.newInstance(); method.invoke(student, "李四" ); System.out.println(student); Method method2 = classzz.getDeclaredMethod("getName" ); Object value = method2.invoke(student); System.out.println(value); } }
5. 获取泛型父类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.itguigu.api;import java.lang.reflect.ParameterizedType;import java.lang.reflect.Type;import org.junit.Test;public class TestNewInstance { @Test public void Test2 () throws Exception { Class clazz = Son.class ; Type genericSuperclass = clazz.getGenericSuperclass(); ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass; Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); for (Type type : actualTypeArguments) { System.out.println(type); } } } abstract class Father <T , U > {} class Son extends Father <String , Integer > {}
6. 获取注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.itguigu.api;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import org.junit.Test;public class TestNewInstance { @Test public void Test2 () throws Exception { Class<?> clazz = MyClass.class ; MyAnntation annotation = clazz.getAnnotation(MyAnntation.class ) ; String value = annotation.value(); System.out.println(value); } } @MyAnntation (value = "张三" )class MyClass { } @Retention (RetentionPolicy.RUNTIME)@interface MyAnntation{ String value () ; }
代理 静态代理 让代理类替被代理类完成一些 “非业务” 代码,核心业务代码还是交给被代理者自己完成。代理模式都有三部分组成,分别是1,主题。2,被代理类。3,代理类。在静态代理中代理类和被代理类需要实现同样的主题接口(下面的 UserDaoProxy 实现了 UserDao 接口),还因为要把核心业务交还给被代理者自己完成,所有在代理类中还应该有被代理者对象的引用(private UserDao target)。
例如给所有的方法都增加一个功能,统计该方法的运行时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 package com.itguigu.proxy;public class TestProxy { public static void main (String[] args) { UserDao userDao = new UserDaoProxy(new UserDaoImpl()); userDao.instert(); } } interface UserDao { void instert () ; void delete () ; void update () ; void search () ; } class UserDaoImpl implements UserDao { @Override public void instert () { System.out.println("insert 数据" ); } @Override public void delete () { System.out.println("delete 数据" ); } @Override public void update () { System.out.println("update 数据" ); } @Override public void search () { System.out.println("search 数据" ); } } class UserDaoProxy implements UserDao { private UserDao target; public UserDaoProxy (UserDao target) { super (); this .target = target; } @Override public void instert () { long start = System.currentTimeMillis(); target.instert(); long end = System.currentTimeMillis(); System.out.println("消耗时间为:" + (end - start)); } @Override public void delete () { long start = System.currentTimeMillis(); target.delete(); long end = System.currentTimeMillis(); System.out.println("消耗时间为:" + (end - start)); } @Override public void update () { long start = System.currentTimeMillis(); target.update(); long end = System.currentTimeMillis(); System.out.println("消耗时间为:" + (end - start)); } @Override public void search () { long start = System.currentTimeMillis(); target.search(); long end = System.currentTimeMillis(); System.out.println("消耗时间为:" + (end - start)); } }
静态代理的缺点就是不够灵活。需要些写很多重复代码。例如现在现在有一个 OrderDaoImpL 也需要为所有的方法时间计算时间的需求,那么代码就需要重新再实现一遍。
动态代理 动态代理和静态代理的区别在于代理类的实现不一样,动态代理的代理类是自动生成的。需要借助 java.lang.reflect.Proxy 类中的 newProxyInstance 方法,该方法的第一个参数是被代理者的类加载器,第二个参数是被代理者实现的接口们,第三个参数是替被代理者完成工作的处理器对象。newProxyInstance 的返回值就是代理类的对象(代理类是在内存中自动生成的)
动态代理中虽然不需要自己实现代理类,但是还需实现 “代理工作处理器”, 代理工作处理器需要实现 InvocationHandler 接口后需要重写 invoke 方法,invoke 方法是自动调用的。其中 invoke 方法的第一个参数 proxy 为代理类对象,第二个参数 method 为代理类要真正执行的方法(例如下面的 insert….),第三个参数 args 是给 method 方法的实参列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 package com.itguigu.proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class TestProxy { public static void main (String[] args) { UserDaoImpl userDaoImpl = new UserDaoImpl(); ClassLoader loader = userDaoImpl.getClass().getClassLoader(); Class<?>[] interfaces = userDaoImpl.getClass().getInterfaces(); Hanlder h = new Hanlder(userDaoImpl); UserDao proxyInstance = (UserDao) Proxy.newProxyInstance(loader, interfaces, h); proxyInstance.update(); } } interface UserDao { void instert () ; void delete () ; void update () ; void search () ; } class UserDaoImpl implements UserDao { @Override public void instert () { System.out.println("insert 数据" ); } @Override public void delete () { System.out.println("delete 数据" ); } @Override public void update () { System.out.println("update 数据" ); } @Override public void search () { System.out.println("search 数据" ); } } class Hanlder implements InvocationHandler { private Object target; public Hanlder (Object target) { super (); this .target = target; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { long start = System.currentTimeMillis(); System.out.println("当前被代理的类为" + proxy.getClass().getName()); Object returnValue = method.invoke(target, args); long end = System.currentTimeMillis(); System.out.println("当前方法:" + method.getName() + "消耗时间为:" + (end - start)); return returnValue; } }